/**
 * Copyright (c) 2008, Keisuke Inoue, Syracuse Unviersity
 * All rights reserved.
 *
 */
package edu.syr.iis.web.model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;

/**
 * @author kinoue
 *
 */
public class DisagreementMatrix
{
	public List<Conversation> conversations;
	
    public User annotator1;
    public User annotator2;
    public ArrayList<DialogueAct> annotations;
    public ArrayList<String> annotationLabels;    
    public int[][] matrix;
    
    public int[] na1;  // NAs for annotator1;
    public int[] na2;  // NAs for annotator2;
    
    public int[] total1;  // Totals for annotator1;
    public int[] total2;  // Totals for annotator2;
    
    public int totalMatched;
    public int level;    
    
    public DisagreementMatrix()
    {
    	
    }
    
    public Collection<String> getTopFiveDisagreementStrings() 
    {
    	return getSortedDisagreementStrings(5);
    }
    
    public Collection<String> getSortedDisagreementStrings()
    {
    	return getSortedDisagreementStrings(-1);
    }

    public Collection<String> getSortedDisagreementStrings(int n)
    {
    	ArrayList<String> strings = new ArrayList<String>();
    	SortedSet<Map.Entry<String, Integer>> disagreements = entriesSortedByValues(getDisagreements());
    
    	int count = 0;
    	for (Map.Entry<String, Integer> entry: disagreements)
    		if (count++ < n || n < 0) //n < 0 is a special case
    			strings.add(entry.getKey() + ":" + entry.getValue());
    	
    	return strings;    		
    }

    
    public static <K,V extends Comparable<? super V>>
    SortedSet<Map.Entry<K,V>> entriesSortedByValues(Map<K,V> map) {
        SortedSet<Map.Entry<K,V>> sortedEntries = new TreeSet<Map.Entry<K,V>>(
            new Comparator<Map.Entry<K,V>>() 
            {
                public int compare(Map.Entry<K,V> e1, Map.Entry<K,V> e2) {
                	// swpaing the e1 and e2, to make it decending order
                	//
                	int res = e2.getValue().compareTo(e1.getValue());
                    return res != 0 ? res : 1;  // this is to handle equal values.
                }
            }
        );
        sortedEntries.addAll(map.entrySet());
        return sortedEntries;
    }

    
    public Collection<String> getDisagreementStrings()
    {
    	ArrayList<String> strings = new ArrayList<String>();
    	TreeMap<String, Integer> disagreements = getDisagreements();
    	
    	for (String key: disagreements.keySet())
    		strings.add(key + ":" + disagreements.get(key));
    	return strings;    		
    }
    
    public TreeMap<String, Integer> getDisagreements()
    {
    	TreeMap<String, Integer> disagreements = new TreeMap<String, Integer>();
    	
    	for (int i = 0; i < matrix.length; i++)
    		for (int j = 0; j < matrix[0].length; j++)
    			if (i != j && matrix[i][j] != 0)
    			{    				
    				String key = annotationLabels.get(i) + " => " + annotationLabels.get(j);
    				Integer value = Integer.valueOf(matrix[i][j]);
    				disagreements.put(key, value);
    			}
    	return disagreements;
    }

    public DisagreementMatrix(User u1, User u2, List<Conversation> list, int l)
    {
		annotator1 = u1; 
		annotator2 = u2;
		conversations = list;
		level = l;
		
		Set<DialogueAct> annotationSet = new HashSet<DialogueAct>();
		Set<String> annotationLabelSet = new HashSet<String>();
		
		// I have to go through the messages twice, because I need to create the matrix first! (there must be a better way though...)
		//
		for (Conversation conversation:conversations) 
		{
			// Going through all the messages to create a set of labels used
			//
			for (Message message: conversation.getMessages())
			{
				annotationSet.addAll(message.getDialogueActs(u1));
				annotationSet.addAll(message.getDialogueActs(u2));
			}
		}

		// Labels are adjusted based on the level of analysis
		//
		for (DialogueAct da: annotationSet)
			annotationLabelSet.add(da.getLabelForAgreement(level));


		annotations = new ArrayList<DialogueAct>(annotationSet);	    
		annotationLabels = new ArrayList<String>(annotationLabelSet);

		matrix = new int[annotationLabels.size()][annotationLabels.size()];
		
		na1 = new int[annotationLabels.size()];
		na2 = new int[annotationLabels.size()];
		
		for (Conversation conversation:conversations) 
		{
			for (Message message: conversation.getMessages())
			{
				SortedSet<DialogueAct> daSet1 = message.getSortedDialogueActs(u1);
				SortedSet<DialogueAct> daSet2 = message.getSortedDialogueActs(u2);

				DialogueAct[] das1 = new DialogueAct[daSet1.size()]; /// stupid conversions because SortedSet does not support access with indexes.
				das1 = daSet1.toArray(das1);

				DialogueAct[] das2 = new DialogueAct[daSet2.size()];  
				das2 = daSet2.toArray(das2);

				int i = 0;
				for (i = 0; i < das1.length && i < das2.length; i++)
				{
					int index1 = annotationLabels.indexOf(das1[i].getLabelForAgreement(level));
					int index2 = annotationLabels.indexOf(das2[i].getLabelForAgreement(level));
					matrix[index1][index2]++;
				}

				// Here I am defining the meaning of "NA" or mechanism of matching the dialogue act from one annotation to 
				// to another. It is basically first-come first-served basis. All the left overs are considered "NA" by the other
				// annotator
				// 
				if (das1.length < das2.length)
					while (i < das2.length)
					{
						int index = annotationLabels.indexOf(das2[i].getLabelForAgreement(level));
						na1[index]++;
						i++;
					}
				else
					while (i < das1.length)
					{
						int index = annotationLabels.indexOf(das1[i].getLabelForAgreement(level));
						na2[index]++;
						i++;
					}
			}	
			
		}		

		setTotals();
		
    }
    
    // This is just to be able to test the Matrix with out setting the conversations
    //
    void setTotals()
    {
    	
		total1 = new int[annotationLabels.size()];
		total2 = new int[annotationLabels.size()];

		for (int i = 0; i < matrix.length; i++)
		{
			for (int j = 0; j < matrix[0].length; j++)
					total1[i] += matrix[i][j];	
			totalMatched += total1[i];
		}
		for (int i = 0; i < matrix[0].length; i++)
			for (int j = 0; j < matrix.length; j++)
				total2[j] += matrix[i][j];
    }
    
    public int getNumOfConversations()
    {
    	return conversations.size();
    }
    
    public int getNumOfMessages()
    {
    	int count = 0;
    	for (Conversation conversation: conversations)
    		count += conversation.getNumMessages();
    	return count;
    }
    
    public int[] getSumsOfMarginals()
    {
    	int[] sumsOfMarginals = new int[matrix.length];
    	for (int i = 0; i < matrix.length; i++)
    	{
    		sumsOfMarginals[i] = total1[i] +  total2[i];
    	}
    	return sumsOfMarginals;
    }
    
    public int[] getProductsOfMarginals()
    {
    	int[] productsOfMarginals = new int[matrix.length];
    	for (int i = 0; i < matrix.length; i++)
    	{
    		productsOfMarginals[i] = total1[i] *  total2[i];  
    	}
    	return productsOfMarginals;
    }
    
    public double getSumOfProductsOfMarginals()
    {
    	double sumOfProductsOfMarginals = 0;
    	
    	for (double productOfMarginals: getProductsOfMarginals())
    		sumOfProductsOfMarginals += productOfMarginals;
    	
    	return sumOfProductsOfMarginals;    	
    }
    
    public double[] getJointMarginalProportions()
    {
    	int[] sumsOfMarginals = getSumsOfMarginals();
    	double[] jointMarginalProportions = new double[sumsOfMarginals.length]; 
    	int grandSum = 0;
    	
    	for (int i = 0; i < sumsOfMarginals.length; i++)
    		grandSum += sumsOfMarginals[i];
    	
    	for (int i = 0; i < sumsOfMarginals.length; i++)
    		jointMarginalProportions[i] = ((double) sumsOfMarginals[i]) / grandSum;
    	return jointMarginalProportions;    	
    }
    
    public double getPi()
    {
    	//
    	// pi = (PA_o - PA_e) / (1 - PA_e)
    	//
    	// whereas PA_e = SUM (joint marginal proportion)^2
    	
    	double pa_e = 0;
    	double pa_o = getAgreementPercentileMatched() / 100;
    	
    	double[] jointMarginalProportions = getJointMarginalProportions();
    	
    	for (int i = 0; i < jointMarginalProportions.length; i++)
    		pa_e += jointMarginalProportions[i] * jointMarginalProportions[i];
    	    		    	
    	return (pa_o - pa_e) / (1 - pa_e);
    	
    }
    
    public double getKappa()
    {
    	//
    	// kappa = (PA_o - PA_e) / (1 - PA_e)
    	//
    	// whereas PA_e = (1 / n^2) * SUM (products of marginals)
    	
    	double sumOfProductsOfMarginals = getSumOfProductsOfMarginals();
    	
       	if (sumOfProductsOfMarginals == 0.0)
    		return 0.0;
    		
    	double pa_e = sumOfProductsOfMarginals / (getMatchedAnnotationCount() * getMatchedAnnotationCount());
    	double pa_o = getAgreementPercentileMatched() / 100;

    	return (pa_o - pa_e) / (1 - pa_e);
    }
    
    public int getMatchedAnnotationCount()
    {
    	return totalMatched;
    }
    
    public float getAgreementPercentileMatched()
    {
    	return ((float) getAgreedAnnotationCount() / 2) /  getMatchedAnnotationCount() * 100; 
    }
    
    
    public float getAgreementPercentile()
    {
    	return ((float) getAgreedAnnotationCount()) / getAnnotationCount() * 100; 
    }
    
    public int getAnnotationCount()
    {
    	return annotations.size();
    }
    
    public int getAgreedAnnotationCount()
    {
    	int count = 0;    	
    	for (int i = 0; i < matrix.length; i++) // you don't want count the NA and Total cells.  
    			count += (matrix[i][i] * 2);  // counting twice. make sense?    	
    	return count;
    }

    public int getDisagreedAnnotationCount()
    {
    	int count = 0;    	
    	for (int i = 0; i < matrix.length; i++) // skip NA and Total cells
    		for (int j = 0; j < matrix.length; j++)
    			if (i != j)
    				count += (matrix[i][j] * 2);    	
    	return count;
    }
        
    public int getNAAnnotationCount()
    {
    	int count = 0;
    	
    	for (int i = 0; i < matrix.length; i++)
    	{
    		count += na1[i];
    		count += na2[i];
    	}
    	return count;
    }
    
	public User getAnnotator1() {
		return annotator1;
	}
	public User getAnnotator2() {
		return annotator2;
	}
	public ArrayList<DialogueAct> getAnnotations() {
		return annotations;
	}
	public ArrayList<String> getAnnotationLabels() {
		return annotationLabels;
	}
	public int[][] getMatrix() {
		return matrix;
	}

	public List<Conversation> getConversations() {
		return conversations;
	}

	public int getLevel() {
		return level;
	}
	
	
	
}
